package com.kim.authorization.server.config;

import com.kim.authorization.server.config.util.AuthProperties;
import com.kim.authorization.server.service.UserLoginServiceImpl;
import com.kim.common.utilcom.utils.BpwdEncoderUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

/**
 * spring security配置
 * <p>在WebSecurityConfigurerAdapter不拦截oauth要开放的资源
 */
@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 获取用户认证信息Service
     */
    private final UserLoginServiceImpl userLoginServiceImpl;

    /**
     * 认证成功处理器
     */
    private final AuthenticationSuccessHandler loginSuccessHandler;

    /**
     * 认证失败处理器
     */
    private final AuthenticationFailureHandler loginFailureHandler;

    /**
     * 自定义权限不足处理器
     */
    private final AccessDeniedHandler accessDeniedHandler;

    public WebSecurityConfig(UserLoginServiceImpl userLoginServiceImpl,
                             AuthenticationSuccessHandler loginSuccessHandler,
                             AuthenticationFailureHandler loginFailureHandler,
                             AccessDeniedHandler accessDeniedHandler) {
        this.userLoginServiceImpl = userLoginServiceImpl;
        this.loginSuccessHandler = loginSuccessHandler;
        this.loginFailureHandler = loginFailureHandler;
        this.accessDeniedHandler = accessDeniedHandler;
    }

    /**
     * Spring Security中HttpSecurity配置程序允许的受保护资源的其他定制
     *
     * <p>注意:如果您的授权服务器也是一个资源服务器，那么还有另一个安全过滤器链，
     * 它的优先级较低，控制了API资源。 对于那些需要通过访问令牌来保护的请求，您需要它们的路径不能与主用户所面对的过滤器链中的那些相匹配，
     * 所以一定要包含一个请求matcher，它只挑选出下面的WebSecurityConfigurer中的非Api资源。
     *
     * @param http HttpSecurity
     **/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义权限不足处理器
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
        /* 开启授权认证 */
        http
                // http security 要拦截的url，这里这拦截 无需 http.csrf().disable();
                .requestMatchers()
                .antMatchers("/oauth/**", AuthProperties.LOGIN_PAGE, AuthProperties.LOGIN_PROCESS_URL)
                .and()
                .authorizeRequests()
                // 允许访问授权接口
                .antMatchers(
                        "/oauth/client/**"
                        , AuthProperties.LOGIN_PAGE
                        , AuthProperties.LOGIN_PROCESS_URL
                        , AuthProperties.AUTHORIZE_CODE).permitAll()
                .anyRequest().authenticated();
        /* 登录配置 */
        http.formLogin()
                // （授权码、sso）网页认证通过处理
                .successHandler(loginSuccessHandler)
                // （授权码、sso）网页认证错误处理（json 交互时开启）
//                .failureHandler(loginFailureHandler)
                // 请求 {用户名} 参数名称
                .usernameParameter(AuthProperties.USERNAME_PARAMETER_NAME)
                // 请求 {密码} 参数名
                .passwordParameter(AuthProperties.PASSWORD_PARAMETER_NAME)
                // 登录页面
                .loginPage(AuthProperties.LOGIN_PAGE)
                // 登录处理url
                .loginProcessingUrl(AuthProperties.LOGIN_PROCESS_URL)
        ;
        // 暂时不使用用Basic
        http.httpBasic().disable();
        /* session 设置为 IF_REQUIRED 有需要才生成 */
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
    }

    /**
     * 全局用户信息
     *
     * @param auth 认证管理
     * @throws Exception 用户认证异常信息
     **/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userLoginServiceImpl).passwordEncoder(passwordEncoder());
    }

    /**
     * 认证管理(授权服务配置需要用到这个)
     *
     * @return 认证管理对象
     * @throws Exception 认证异常信息
     **/
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置密码加密 （使用spring默认）
     *
     * @return org.springframework.security.crypto.password.PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            /**
             * 加密密码
             * <p>
             *     如果为空则不加密.
             *     拓展手机号验证码等验证方式后
             *     除用户名密码方式其余密码为空不加密返回空串.
             * </p>
             * @param charSequence 原始密码密码
             * @return java.lang.String
             */
            @Override
            public String encode(CharSequence charSequence) {
                if ("".contentEquals(charSequence)) {
                    return "";
                }
                return BpwdEncoderUtil.bCryptPassword((String) charSequence);
            }

            /**
             * 验证密码是否正确
             * <p>
             *     原密码与密文都为空时则属于扩展登录，
             *     扩展登录只校验相应规则，不校验密码.
             *     密码模式密码登录、授权码登录的获取授权码步骤 与 隐式模式 校验密码
             * </p>
             * @param charSequence 未加密的原密码
             * @param s            密文
             * @return boolean
             */
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                if ("".contentEquals(charSequence) && "".equals(s)) {
                    return true;
                }
                return BpwdEncoderUtil.matches(charSequence, s);
            }
        };
    }

    public static void main(String[] args) {
        log.info(new BCryptPasswordEncoder().encode("secret"));
        log.info(new BCryptPasswordEncoder().encode("tourist"));
        log.info(new BCryptPasswordEncoder().encode("123"));
    }

}
